﻿' ----------------------------------------------------------------------------------
' Microsoft Developer & Platform Evangelism
' 
' Copyright (c) Microsoft Corporation. All rights reserved.
' 
' THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
' OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ----------------------------------------------------------------------------------
' The example companies, organizations, products, domain names,
' e-mail addresses, logos, people, places, and events depicted
' herein are fictitious.  No association with any real company,
' organization, product, domain name, email address, logo, person,
' places, or events is intended or should be inferred.
' ----------------------------------------------------------------------------------

Imports System.Data.Services.Client
Imports System.IO.IsolatedStorage
Imports System.Windows.Threading
Imports Microsoft.Samples.WindowsPhoneCloud.Phone.Push
Imports Microsoft.Samples.WindowsPhoneCloud.StorageClient
Imports Microsoft.Samples.WindowsPhoneCloud.StorageClient.Credentials
Imports System.Globalization

Public Class CloudClientFactory
    Implements ICloudClientFactory

    Private ReadOnly phoneState As IDictionary(Of String, Object)
    Private ReadOnly isolatedStorage As IDictionary(Of String, Object)
    Private ReadOnly appResources As IDictionary(Of Object, Object)
    Private ReadOnly dispatcher As Dispatcher

    Private ReadOnly tableServiceContextDictionary As IDictionary(Of String, ITableServiceContext) = New Dictionary(Of String, ITableServiceContext)()
    Private ReadOnly cloudTableClientDictionary As IDictionary(Of String, ICloudTableClient) = New Dictionary(Of String, ICloudTableClient)()
    Private ReadOnly cloudBlobClientDictionary As IDictionary(Of String, ICloudBlobClient) = New Dictionary(Of String, ICloudBlobClient)()
    Private ReadOnly cloudQueueClientDictionary As IDictionary(Of String, ICloudQueueClient) = New Dictionary(Of String, ICloudQueueClient)()

    Public Sub New()
        Me.New(PhoneApplicationService.Current.State, IsolatedStorageSettings.ApplicationSettings, Application.Current.Resources, Deployment.Current.Dispatcher)
    End Sub

    Public Sub New(ByVal phoneState As IDictionary(Of String, Object), ByVal isolatedStorage As IDictionary(Of String, Object), ByVal appResources As IDictionary(Of Object, Object), ByVal dispatcher As Dispatcher)
        Me.phoneState = phoneState
        Me.isolatedStorage = isolatedStorage
        Me.appResources = appResources
        Me.dispatcher = dispatcher

        ' Initialize the PushContext.Current instance.
        If PushContext.Current Is Nothing Then
            Dim TempPushContext As PushContext = New PushContext(Me.appResources("PushChannelName").ToString(), Me.appResources("PushServiceName").ToString(), {New Uri("https://127.0.0.1")}, Me.dispatcher)
        End If
    End Sub

#If ACS Then
    Public ReadOnly Property TokenStore() As SL.Phone.Federation.Utilities.RequestSecurityTokenResponseStore Implements ICloudClientFactory.TokenStore
        Get
            Return TryCast(Me.appResources("rstrStore"), SL.Phone.Federation.Utilities.RequestSecurityTokenResponseStore)
        End Get
    End Property
#Else
    Public ReadOnly Property UserName() As String Implements ICloudClientFactory.UserName
        Get
            Return Me.GetValue(Of String)("UserName")
        End Get
    End Property

    Public ReadOnly Property AuthenticationToken() As String Implements ICloudClientFactory.AuthenticationToken
        Get
            Return Me.GetValue(Of String)("AuthenticationToken")
        End Get
    End Property
#End If

    Public Function ResolveTablesServiceContext(Optional ByVal key As String = "") As ITableServiceContext Implements ICloudClientFactory.ResolveTablesServiceContext
        If String.IsNullOrEmpty(key) Then
            Return New TableServiceContext(Me.appResources("AzureStorageTableProxyEndpoint").ToString(), Me.ResolveStorageCredentials())
        End If

        If Not Me.tableServiceContextDictionary.ContainsKey(key) Then
            Me.tableServiceContextDictionary.Add(key, New TableServiceContext(Me.appResources("AzureStorageTableProxyEndpoint").ToString(), Me.ResolveStorageCredentials()))
        End If

        Return Me.tableServiceContextDictionary(key)
    End Function

    Public Function ResolveDataServiceCollection(Of T)(Optional ByVal key As String = "") As DataServiceCollection(Of T) Implements ICloudClientFactory.ResolveDataServiceCollection
        Return New DataServiceCollection(Of T)(TryCast(Me.ResolveTablesServiceContext(key), DataServiceContext))
    End Function

    Public Function ResolveCloudTableClient(Optional ByVal key As String = "") As ICloudTableClient Implements ICloudClientFactory.ResolveCloudTableClient
        If String.IsNullOrEmpty(key) Then
            Return New CloudTableClient(Me.ResolveTablesServiceContext(key))
        End If

        If Not Me.cloudTableClientDictionary.ContainsKey(key) Then
            Me.cloudTableClientDictionary.Add(key, New CloudTableClient(Me.ResolveTablesServiceContext(key)))
        End If

        Return Me.cloudTableClientDictionary(key)
    End Function

    Public Function ResolveCloudBlobClient(Optional ByVal key As String = "") As ICloudBlobClient Implements ICloudClientFactory.ResolveCloudBlobClient
        If String.IsNullOrEmpty(key) Then
            Return CreateCloudBlobClient()
        End If

        If Not Me.cloudBlobClientDictionary.ContainsKey(key) Then
            Me.cloudBlobClientDictionary.Add(key, CreateCloudBlobClient())
        End If

        Return Me.cloudBlobClientDictionary(key)
    End Function

    Public Function ResolveCloudQueueClient(Optional ByVal key As String = "") As ICloudQueueClient Implements ICloudClientFactory.ResolveCloudQueueClient
        If String.IsNullOrEmpty(key) Then
            Return Me.CreateCloudQueueClient()
        End If

        If Not Me.cloudQueueClientDictionary.ContainsKey(key) Then
            Me.cloudQueueClientDictionary.Add(key, Me.CreateCloudQueueClient())
        End If

        Return Me.cloudQueueClientDictionary(key)
    End Function

    Public Function ResolvePushNotificationClient() As ISamplePushUserRegistrationClient Implements ICloudClientFactory.ResolvePushNotificationClient
        Return New SamplePushUserRegistrationClient(New Uri(Me.appResources("PushNotificationServiceEndpoint").ToString()), Me.ResolveStorageCredentials(), Me.appResources("ApplicationId").ToString())
    End Function

    Public Function ResolveOdataServiceContext() As DataServiceContext Implements ICloudClientFactory.ResolveOdataServiceContext
        Dim serviceUri = New Uri(Me.appResources("SqlOdataEndpoint").ToString())
        Dim context = New DataServiceContext(serviceUri)

        AddHandler _
            context.SendingRequest, _
            Sub(s, e)
                Dim credentials = Me.ResolveStorageCredentials()
                credentials.AddAuthenticationHeadersLite(e.RequestData, e.RequestHeaders)
            End Sub


        Return context
    End Function

#If ACS Then

    Public Function ResolveStorageCredentials() As IStorageCredentials
        Return New StorageCredentialsSwtToken(Me.TokenStore.SecurityToken, If(Me.TokenStore.RequestSecurityTokenResponse IsNot Nothing, Me.TokenStore.RequestSecurityTokenResponse.expires, 0))
    End Function

    Public Function ResolveRegistrationClient() As IRegistrationClient Implements ICloudClientFactory.ResolveRegistrationClient
        Return New RegistrationClient(Me.appResources("RegistrationServiceEndpoint").ToString(), Me.ResolveStorageCredentials())
    End Function

    Public Sub CleanTablesClientsKey(ByVal key As String) Implements ICloudClientFactory.CleanTablesClientsKey
        If Not String.IsNullOrEmpty(key) Then
            If Me.tableServiceContextDictionary.ContainsKey(key) Then
                Me.tableServiceContextDictionary.Remove(key)
            End If

            If Me.cloudTableClientDictionary.ContainsKey(key) Then
                Me.cloudTableClientDictionary.Remove(key)
            End If
        End If
    End Sub

    Public Sub CleanAuthenticationToken() Implements ICloudClientFactory.CleanAuthenticationToken
        ' Remove the authentication token from the phone state dictionary.
        If Me.TokenStore.ContainsValidRequestSecurityTokenResponse() Then
            Me.TokenStore.RequestSecurityTokenResponse = Nothing
        End If

        ' Clear the current state of the factory.
        Me.tableServiceContextDictionary.Clear()
        Me.cloudTableClientDictionary.Clear()
        Me.cloudBlobClientDictionary.Clear()
        Me.cloudQueueClientDictionary.Clear()
    End Sub

    Private Function CreateCloudBlobClient() As ICloudBlobClient
        ' To use the Shared Access Signature to access blobs and containers.
        Return New CloudBlobClient(New SharedAccessSignatureServiceClient(Me.appResources("SharedAccessSignatureServiceEndpoint").ToString(), Me.ResolveStorageCredentials()))
    End Function

    Private Function CreateCloudQueueClient() As ICloudQueueClient
        Return New CloudQueueClient(Me.appResources("AzureStorageQueueProxyEndpoint").ToString(), Me.ResolveStorageCredentials())
    End Function
#Else

    Public Function ResolveStorageCredentials() As IStorageCredentials
        Return New StorageCredentialsAuthToken(Me.AuthenticationToken)
    End Function

    Public Function ResolveAuthenticationClient() As IAuthenticationClient Implements ICloudClientFactory.ResolveAuthenticationClient
        Return New AuthenticationClient(Me.appResources("AuthenticationServiceEndpoint").ToString())
    End Function

    Public Sub VerifyLoggedIn(ByVal userAlreadyLoggedInCallback As Action, ByVal userNotLoggedInCallback As Action) Implements ICloudClientFactory.VerifyLoggedIn
        Dim authToken = Me.AuthenticationToken
        If String.IsNullOrEmpty(authToken) Then
            Me.dispatcher.BeginInvoke(Sub() userNotLoggedInCallback())
            Return
        End If

        Me.ResolveAuthenticationClient().Validate(
            authToken,
            Sub(args) Me.dispatcher.BeginInvoke(
                Sub() userAlreadyLoggedInCallback()),
                Function(args) Me.dispatcher.BeginInvoke(Sub() userNotLoggedInCallback()))
    End Sub

    Public Sub SetUserName(ByVal userName As String, Optional ByVal persist As Boolean = False) Implements ICloudClientFactory.SetUserName
        If String.IsNullOrEmpty(userName) Then
            Throw New ArgumentNullException("userName", "The userName cannot be null nor empty.")
        End If

        Me.SaveValue(userName, "UserName", persist)
    End Sub

    Public Sub SetAuthenticationToken(ByVal authToken As String, Optional ByVal persist As Boolean = False) Implements ICloudClientFactory.SetAuthenticationToken
        If String.IsNullOrEmpty(authToken) Then
            Throw New ArgumentNullException("authToken", "The authentication token cannot be null nor empty.")
        End If

        Me.SaveValue(authToken, "AuthenticationToken", persist)
    End Sub

    Public Sub CleanTablesClientsKey(ByVal key As String) Implements ICloudClientFactory.CleanTablesClientsKey
        If Not String.IsNullOrEmpty(key) Then
            If Me.tableServiceContextDictionary.ContainsKey(key) Then
                Me.tableServiceContextDictionary.Remove(key)
            End If

            If Me.cloudTableClientDictionary.ContainsKey(key) Then
                Me.cloudTableClientDictionary.Remove(key)
            End If
        End If
    End Sub

    Public Sub CleanAuthenticationToken() Implements ICloudClientFactory.CleanAuthenticationToken
        ' Remove the authentication token from the phone state dictionary.
        If Me.phoneState.ContainsKey("AuthenticationToken") Then
            Me.phoneState.Remove("AuthenticationToken")
        End If

        ' Remove the authentication token from the phone isolated storage.
        If Me.isolatedStorage.ContainsKey("AuthenticationToken") Then
            Me.isolatedStorage.Remove("AuthenticationToken")
        End If

        ' Clear the current state of the factory.
        Me.tableServiceContextDictionary.Clear()
        Me.cloudTableClientDictionary.Clear()
        Me.cloudBlobClientDictionary.Clear()
        Me.cloudQueueClientDictionary.Clear()
    End Sub

    Private Function CreateCloudBlobClient() As ICloudBlobClient
        ' To use the Shared Access Signature to access blobs and containers.
        Return New CloudBlobClient(New SharedAccessSignatureServiceClient(Me.appResources("SharedAccessSignatureServiceEndpoint").ToString(), Me.ResolveStorageCredentials()))
    End Function

    Private Function CreateCloudQueueClient() As ICloudQueueClient
        Return New CloudQueueClient(Me.appResources("AzureStorageQueueProxyEndpoint").ToString(), Me.ResolveStorageCredentials())
    End Function

    Private Sub SaveValue(ByVal value As Object, ByVal key As String, ByVal persist As Boolean)
        ' Save the value in the phone application state.
        If Me.phoneState.ContainsKey(key) Then
            Me.phoneState.Remove(key)
        End If

        Me.phoneState.Add(key, value)

        If persist Then
            ' Persist the value in the phone isolated storage.
            Me.isolatedStorage(key) = value
        Else
            ' Remove old value from the phone isolated storage.
            If Me.isolatedStorage.ContainsKey(key) Then
                Me.isolatedStorage.Remove(key)
            End If
        End If
    End Sub

    Private Function GetValue(Of T)(ByVal key As String) As T
        If Me.phoneState.ContainsKey(key) Then
            ' Get the value from the phone application state.
            Return CType(Me.phoneState(key), T)
        End If

        If Me.isolatedStorage.ContainsKey(key) Then
            ' Get the value from the phone isolated storage.
            Return CType(Me.isolatedStorage(key), T)
        End If

        Return Nothing
    End Function
#End If
End Class
